If we want to identify groups or clusters using expression data, we often want to validate those groups in some way, such as determining the “correct” number of groups or checking for agreement within clusters.

In this notebook, we’ll use the medulloblastoma data from the OpenPBTA project to look at two techniques for cluster validation and look how clusters overlap with sample labels that are available in our metadata.

Set up

Libraries

# Bit o' data wranglin'
library(tidyverse)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ─────────────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
✓ ggplot2 3.3.0     ✓ purrr   0.3.4
✓ tibble  3.0.1     ✓ dplyr   0.8.3
✓ tidyr   1.0.0     ✓ stringr 1.4.0
✓ readr   1.3.1     ✓ forcats 0.4.0
package ‘readr’ was built under R version 3.6.2package ‘dplyr’ was built under R version 3.6.2── Conflicts ────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
# Consensus clustering library
library(ConsensusClusterPlus)
# Library we'll use for silhouette values
library(cluster)
library(ComplexHeatmap)
Loading required package: grid
========================================
ComplexHeatmap version 2.2.0
Bioconductor page: http://bioconductor.org/packages/ComplexHeatmap/
Github page: https://github.com/jokergoo/ComplexHeatmap
Documentation: http://jokergoo.github.io/ComplexHeatmap-reference

If you use it in published research, please cite:
Gu, Z. Complex heatmaps reveal patterns and correlations in multidimensional 
  genomic data. Bioinformatics 2016.
========================================
# We'll make a 3D plot with this library
library(plotly)  # TODO: install globally
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Attaching package: ‘plotly’

The following object is masked from ‘package:ComplexHeatmap’:

    add_heatmap

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout

Directories and files

Directories

# Directory with the prepared/lightly cleaned OpenPBTA data
data_dir <- file.path("data", "open-pbta", "processed")

# Create a directory to hold our cluster validation results if it doesn't
# exist yet
results_dir <- "results"
if (!dir.exists(results_dir)) {
  dir.create(results_dir, recursive = TRUE)
}

Input files

histologies_file <- file.path(data_dir, "pbta-histologies-stranded-rnaseq.tsv")
rnaseq_file <- file.path(data_dir, "pbta-vst-stranded.tsv")

Output files

cc_results_file <- file.path(results_dir, "consensus_clustering_results.RDS")

Read in and filter data

Sample metadata

Let’s read in the sample metadata and get the sample identifiers for medulloblastoma samples.

# Read in metadata TSV
histologies_df <- read_tsv(histologies_file)
Parsed with column specification:
cols(
  .default = col_character(),
  OS_days = col_double(),
  age_last_update_days = col_double()
)
See spec(...) for full column specifications.
# Only pull out sample identifiers (KidsFirst biospecimen identifiers) that
# correspond to medulloblastoma samples
medulloblastoma_samples <- histologies_df %>%
  filter(short_histology == "Medulloblastoma") %>%
  pull(Kids_First_Biospecimen_ID)

RNA-seq data

# Read in transformed RNA-seq data
rnaseq_df <- read_tsv(rnaseq_file)
Parsed with column specification:
cols(
  .default = col_double(),
  gene_id = col_character()
)
See spec(...) for full column specifications.
# For our clustering validation analyses, we want a matrix of only 
# medulloblastoma samples where the gene identifiers are rownames rather than
# in the first column
rnaseq_mat <- rnaseq_df %>%
  # This makes sure we retain all of the columns with biospecimen IDs that 
  # correspond to medulloblastoma samples
  select(gene_id, all_of(medulloblastoma_samples)) %>%
  tibble::column_to_rownames("gene_id") %>%
  as.matrix()

Cluster validation

Consensus clustering

The first method we’ll use is called consensus clustering. Consensus clustering to finds the “consensus” across multiple runs of the algorithm using a resampling procedure.

We’ll use the package ConsensusClusterPlus that we loaded up top.

The consensus clustering methodology was first introduced in Monti et al. Machine Learning. 2003.

Consensus clustering is one way to help you determine the number of clusters in your data, but it is not the only methodology available. Check out the Data Novia course Cluster Validation Essentials by Alboukadel Kassambara for a deeper dive.

cc_results <- ConsensusClusterPlus(rnaseq_mat,
                                   maxK = 15,
                                   # Setting this seed is necessary for the 
                                   # results to be reproducible
                                   seed = 2020,
                                   innerLinkage = "average",
                                   finalLinkage = "average",
                                   distance = "pearson")
end fraction

It seems like there are 3 main stable clusters from the consensus clustering results. A cluster of a few samples may not be that helpful in reaching our analysis goals.

Let’s take a look at the class labels for k = 9.

# table() creates a contingency table of counts
# 
table(cc_results[[9]]$consensusClass)

 1  2  3  4  5  6  7  8  9 
76  1 25 11  2  2  2  1  1 

(Note: the numbering of the clusters is arbitrary here.)

Let’s consider clusters 1, 3, and 4 for further analysis. But first, we’ll save the entirety of the consensus clustering results to file.

# Write consensus clustering results to file
write_rds(cc_results, path = cc_results_file)

And now to extract the samples in the clusters of interest. What we used table() on before is actually a named vector.

cc_cluster_labels <- cc_results[[9]]$consensusClass
head(cc_cluster_labels)
BS_09Z7TC35 BS_1AYRM596 BS_1BWP5MCT BS_1QXEC43H BS_1TWCV047 BS_2SFTWNVE 
          1           1           2           3           1           1 

We’ll extract the names (biospecimen IDs) for samples in clusters 1, 3, and 4, which contain the majority of samples.

sample_index <- which(cc_cluster_labels %in% c(1, 3, 4))
samples_in_clusters <- names(cc_cluster_labels)[sample_index]

Check what proportion of total samples are in one of these three clusters.

# number of samples that met our logical criterion above divided by total 
# number of samples
length(sample_index) / length(cc_cluster_labels)
[1] 0.9256198

And now we’ll filter the RNA-seq matrix.

filtered_rnaseq_mat <- rnaseq_mat[, samples_in_clusters]
dim(filtered_rnaseq_mat)
[1] 50328   112

Silhouette coefficient

The silhouette coefficient or value is a measure of cluster consistency that ranges from -1 to 1. It is calculated on a per-sample basis – it measures how similar a sample is to the cluster it’s in compared to other clusters (Wikipedia entry for silhouette).

We’ll use the cluster package to compute our silhouette values for the clusters we identified using consensus clustering above.

The silhouette() function takes the class labels and dissimilarities. Let’s use Pearson correlation as we have been throughout.

all.equal(names(cc_cluster_labels[sample_index]), 
          colnames(filtered_rnaseq_mat))
[1] TRUE
# Calculate the Pearson correlation between samples (columns of the matrix)
mb_sample_correlation <- cor(filtered_rnaseq_mat,
                             use = "pairwise.complete.obs",
                             method = "pearson")
# Dissimilarity means we need to subtract these values from 1
mb_sample_dist <- as.dist(1 - mb_sample_correlation)

Let’s calculate the silhouette values and then plot them.

silhouette_results <- silhouette(x = cc_cluster_labels[sample_index],
                                 dist = mb_sample_dist)
plot(silhouette_results)

Compare clustering results to external metadata labels

There are multiple medulloblastoma molecular subtypes and this classification largely relies on gene expression data. A medulloblastoma subtype classifier, which is an example of supervised machine learning, has been applied to the medulloblastoma samples included in OpenPBTA. How do the subtype labels from this classifier (in the molecular_subtype column of our sample metadata) stack up to the clusters we identified with unsupervised methods?

Let’s first make a data frame that holds the subtype labels. We can use this both to compare our unsupervised clustering results to the subtype labels and for some plotting downstream.

mb_molecular_subtype_df <- histologies_df %>%
  filter(short_histology == "Medulloblastoma") %>%
  select(Kids_First_Biospecimen_ID, molecular_subtype)

Add the consensus clustering labels.

# Create a data frame that contains the consensus cluster results and join
# it with the data frame of molecular subtype labels
cc_df <- data.frame(cc_cluster_labels) %>%
  tibble::rownames_to_column("Kids_First_Biospecimen_ID") %>%
  inner_join(mb_molecular_subtype_df)
Joining, by = "Kids_First_Biospecimen_ID"

Do the consensus clustering results agree with the molecular subtype labels?

table(cc_df$cc_cluster_labels, cc_df$molecular_subtype)
   
    Group3 Group4 SHH WNT
  1     13     55   6   2
  2      1      0   0   0
  3      0      7  18   0
  4      1      2   0   8
  5      0      0   1   1
  6      0      0   2   0
  7      0      2   0   0
  8      0      0   1   0
  9      0      1   0   0

Hm… there’s some agreement with the subtype labels but it’s not perfect. Why might that be? Let’s start by looking at the overview figure for the medulloblastoma classifier.

We used a different measure (VST values vs. FPKM) and used all features.

Low-dimensional representation

Let’s see if low-dimensional representations agree with our other unsupervised results. First, let’s find the high variance genes; we’ll perform dimension reduction on those genes only.

# Calculate variance
gene_variance <- matrixStats::rowVars(rnaseq_mat)
# Find the value that we'll use as a threshold to filter the top 5%
variance_threshold <- quantile(gene_variance, 0.95)
# Row indices of high variance genes
high_variance_index <- which(gene_variance > variance_threshold)

UMAP

First, let’s use UMAP.

# Set seed for reproducible UMAP results
set.seed(2020)
# umap() expects features (genes) to be columns, so we have to use t()
umap_results <- umap::umap(t(rnaseq_mat[high_variance_index, ]))

The UMAP coordinates are in the layout element of the list returned by umap::umap().

# Make a data frame of the layout results and join with molecular subtype 
# data frame
umap_plot_df <- data.frame(umap_results$layout) %>%
  tibble::rownames_to_column("Kids_First_Biospecimen_ID") %>%
  inner_join(mb_molecular_subtype_df)
Joining, by = "Kids_First_Biospecimen_ID"

Let’s make a scatter plot and color our samples by the subtype labels.

umap_plot_df %>%
  ggplot(aes(x = X1, 
             y = X2,
             color = molecular_subtype)) +
  geom_point() +
  colorblindr::scale_color_OkabeIto() +
  theme_bw() +
  xlab("UMAP1") +
  ylab("UMAP2")

PCA

Now, we’ll briefly show you how to use built-in functions for PCA on any matrix that’s not in a specialized object for some kind of genomic data.

# Like umap(), prcomp() expects the features to be columns
pca_results <- prcomp(t(rnaseq_mat[high_variance_index, ]), 
                      scale = TRUE)
# The low-dimensional representation is returned in x
pca_results$x[1:6, 1:6]
                   PC1        PC2        PC3       PC4        PC5         PC6
BS_09Z7TC35 -10.305658 -30.486253  21.016270 -1.004493  11.720278  -8.9093995
BS_1AYRM596 -12.936747   4.858999   7.484443 -9.923455   1.800493  14.2377837
BS_1BWP5MCT  -3.198626 -37.832010   9.673423 11.902507   7.611624 -11.4215716
BS_1QXEC43H  37.056562  13.450708   7.197838  1.012471   5.170360  -5.4456082
BS_1TWCV047 -17.895880 -13.279184   1.373011 14.065867   6.912049  -0.1576687
BS_2SFTWNVE -15.486259  21.313599 -14.310736 13.851413 -12.197892   3.5925658

Let’s get the first 10 PCs ready for plotting.

pca_plot_df <- data.frame(pca_results$x[, 1:10]) %>%
  tibble::rownames_to_column("Kids_First_Biospecimen_ID") %>%
  inner_join(mb_molecular_subtype_df)
Joining, by = "Kids_First_Biospecimen_ID"

And now make a scatterplot of PC1 and PC2.

pca_plot_df %>%
  ggplot(aes(x = PC1, 
             y = PC2,
             color = molecular_subtype)) +
  geom_point() +
  colorblindr::scale_color_OkabeIto() +
  theme_bw() +
  xlab("PC1") +
  ylab("PC2")

What about PC2 and PC3?

pca_plot_df %>%
  ggplot(aes(x = PC2, 
             y = PC3,
             color = molecular_subtype)) +
  geom_point() +
  colorblindr::scale_color_OkabeIto() +
  theme_bw() +
  xlab("PC2") +
  ylab("PC3")

summary() will report the proportion of variance explained by each principal component. By accessing the importance element with <summary results>$importance, we can use indexing to only look at the first 10 PCs.

# Save summary of the PCA results
pca_summary <- summary(pca_results)
# Importance information for the first 10 PCs
pca_importance <- pca_summary$importance[, 1:10]
pca_importance
                            PC1      PC2      PC3      PC4      PC5     PC6      PC7     PC8      PC9     PC10
Standard deviation     21.76396 17.86412 14.80238 12.21540 9.649902 8.56367 7.710388 7.63968 6.859131 6.545061
Proportion of Variance  0.18819  0.12679  0.08705  0.05928 0.037000 0.02914 0.023620 0.02319 0.018690 0.017020
Cumulative Proportion   0.18819  0.31498  0.40203  0.46131 0.498310 0.52745 0.551060 0.57425 0.592950 0.609960

plotly is a package that allows us to plot interactive 3D scatterplots.

# plot_ly is kind of like ggplot(), but it doesn't require us to specify the 
# aesthetics or types (e.g., geoms) separately in the same way
pca_3d_plot <- plot_ly(data = pca_plot_df,
                       x = ~ PC1,
                       y = ~ PC2,
                       z = ~ PC3,
                       mode = "markers",
                       type = "scatter3d",
                       color = ~ molecular_subtype,
                       colors = colorblindr::palette_OkabeIto[1:4])

# Add axis labels
pca_3d_plot <- pca_3d_plot %>% 
  layout(scene = list(
    xaxis = list(title = "PC1"),
    yaxis = list(title = "PC2"),
    zaxis = list(title = "PC3")
  ))

pca_3d_plot

Session Info

sessionInfo()
R version 3.6.1 (2019-07-05)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.3 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/atlas/libblas.so.3.10.3
LAPACK: /usr/lib/x86_64-linux-gnu/atlas/liblapack.so.3.10.3

locale:
 [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8        LC_COLLATE=C.UTF-8    
 [5] LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8    LC_PAPER=C.UTF-8       LC_NAME=C             
 [9] LC_ADDRESS=C           LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   

attached base packages:
[1] grid      stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] plotly_4.9.2.1              ComplexHeatmap_2.2.0        cluster_2.1.0              
 [4] ConsensusClusterPlus_1.50.0 forcats_0.4.0               stringr_1.4.0              
 [7] dplyr_0.8.3                 purrr_0.3.4                 readr_1.3.1                
[10] tidyr_1.0.0                 tibble_3.0.1                ggplot2_3.3.0              
[13] tidyverse_1.3.0            

loaded via a namespace (and not attached):
 [1] Biobase_2.46.0      httr_1.4.1          jsonlite_1.6.1      viridisLite_0.3.0   modelr_0.1.5       
 [6] assertthat_0.2.1    askpass_1.1         cellranger_1.1.0    yaml_2.2.1          pillar_1.4.4       
[11] backports_1.1.7     lattice_0.20-38     glue_1.4.1          reticulate_1.14     digest_0.6.25      
[16] RColorBrewer_1.1-2  rvest_0.3.5         colorspace_1.4-1    htmltools_0.4.0     Matrix_1.2-17      
[21] pkgconfig_2.0.3     GetoptLong_0.1.8    broom_0.5.6         haven_2.2.0         scales_1.1.0       
[26] RSpectra_0.16-0     openssl_1.4.1       farver_2.0.3        generics_0.0.2      ellipsis_0.3.1     
[31] withr_2.2.0         umap_0.2.6.0        BiocGenerics_0.32.0 lazyeval_0.2.2      cli_2.0.2          
[36] magrittr_1.5        crayon_1.3.4        readxl_1.3.1        fs_1.4.1            fansi_0.4.1        
[41] nlme_3.1-140        xml2_1.3.1          tools_3.6.1         data.table_1.12.8   hms_0.5.3          
[46] GlobalOptions_0.1.1 lifecycle_0.2.0     matrixStats_0.55.0  munsell_0.5.0       reprex_0.3.0       
[51] compiler_3.6.1      rlang_0.4.6         rstudioapi_0.11     rjson_0.2.20        htmlwidgets_1.5.1  
[56] circlize_0.4.8      crosstalk_1.1.0.1   labeling_0.3        colorblindr_0.1.0   gtable_0.3.0       
[61] DBI_1.1.0           R6_2.4.1            lubridate_1.7.4     knitr_1.28          clue_0.3-57        
[66] shape_1.4.4         stringi_1.4.6       parallel_3.6.1      Rcpp_1.0.4.6        vctrs_0.3.1        
[71] png_0.1-7           dbplyr_1.4.2        tidyselect_1.1.0    xfun_0.14          
LS0tCnRpdGxlOiAiT3BlblBCVEE6IENsdXN0ZXIgdmFsaWRhdGlvbiIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKYXV0aG9yOiBDQ0RMIGZvciBBTFNGCmRhdGU6IDIwMjAKLS0tCgpJZiB3ZSB3YW50IHRvIGlkZW50aWZ5IGdyb3VwcyBvciBjbHVzdGVycyB1c2luZyBleHByZXNzaW9uIGRhdGEsIHdlIG9mdGVuIHdhbnQgdG8gKip2YWxpZGF0ZSoqIHRob3NlIGdyb3VwcyBpbiBzb21lIHdheSwgc3VjaCBhcyBkZXRlcm1pbmluZyB0aGUgImNvcnJlY3QiIG51bWJlciBvZiBncm91cHMgb3IgY2hlY2tpbmcgZm9yIGFncmVlbWVudCB3aXRoaW4gY2x1c3RlcnMuCgpJbiB0aGlzIG5vdGVib29rLCB3ZSdsbCB1c2UgdGhlIG1lZHVsbG9ibGFzdG9tYSBkYXRhIGZyb20gdGhlIE9wZW5QQlRBIHByb2plY3QgdG8gbG9vayBhdCB0d28gdGVjaG5pcXVlcyBmb3IgY2x1c3RlciB2YWxpZGF0aW9uIGFuZCBsb29rIGhvdyBjbHVzdGVycyBvdmVybGFwIHdpdGggc2FtcGxlIGxhYmVscyB0aGF0IGFyZSBhdmFpbGFibGUgaW4gb3VyIG1ldGFkYXRhLgoKIyMgU2V0IHVwCgojIyMgTGlicmFyaWVzCgpgYGB7ciBsaWJyYXJpZXN9CiMgQml0IG8nIGRhdGEgd3JhbmdsaW4nCmxpYnJhcnkodGlkeXZlcnNlKQojIENvbnNlbnN1cyBjbHVzdGVyaW5nIGxpYnJhcnkKbGlicmFyeShDb25zZW5zdXNDbHVzdGVyUGx1cykKIyBMaWJyYXJ5IHdlJ2xsIHVzZSBmb3Igc2lsaG91ZXR0ZSB2YWx1ZXMKbGlicmFyeShjbHVzdGVyKQpsaWJyYXJ5KENvbXBsZXhIZWF0bWFwKQojIFdlJ2xsIG1ha2UgYSAzRCBwbG90IHdpdGggdGhpcyBsaWJyYXJ5CmxpYnJhcnkocGxvdGx5KSAgIyBUT0RPOiBpbnN0YWxsIGdsb2JhbGx5CmBgYAoKIyMjIERpcmVjdG9yaWVzIGFuZCBmaWxlcwoKIyMjIyBEaXJlY3RvcmllcwoKYGBge3IgZGlyZWN0b3JpZXMsIGxpdmUgPSBUUlVFfQojIERpcmVjdG9yeSB3aXRoIHRoZSBwcmVwYXJlZC9saWdodGx5IGNsZWFuZWQgT3BlblBCVEEgZGF0YQpkYXRhX2RpciA8LSBmaWxlLnBhdGgoImRhdGEiLCAib3Blbi1wYnRhIiwgInByb2Nlc3NlZCIpCgojIENyZWF0ZSBhIGRpcmVjdG9yeSB0byBob2xkIG91ciBjbHVzdGVyIHZhbGlkYXRpb24gcmVzdWx0cyBpZiBpdCBkb2Vzbid0CiMgZXhpc3QgeWV0CnJlc3VsdHNfZGlyIDwtICJyZXN1bHRzIgppZiAoIWRpci5leGlzdHMocmVzdWx0c19kaXIpKSB7CiAgZGlyLmNyZWF0ZShyZXN1bHRzX2RpciwgcmVjdXJzaXZlID0gVFJVRSkKfQpgYGAKCiMjIyMgSW5wdXQgZmlsZXMKCmBgYHtyIGlucHV0X2ZpbGVzfQpoaXN0b2xvZ2llc19maWxlIDwtIGZpbGUucGF0aChkYXRhX2RpciwgInBidGEtaGlzdG9sb2dpZXMtc3RyYW5kZWQtcm5hc2VxLnRzdiIpCnJuYXNlcV9maWxlIDwtIGZpbGUucGF0aChkYXRhX2RpciwgInBidGEtdnN0LXN0cmFuZGVkLnRzdiIpCmBgYAoKIyMjIyBPdXRwdXQgZmlsZXMKCmBgYHtyIG91dHB1dF9maWxlcywgbGl2ZSA9IFRSVUV9CmNjX3Jlc3VsdHNfZmlsZSA8LSBmaWxlLnBhdGgocmVzdWx0c19kaXIsICJjb25zZW5zdXNfY2x1c3RlcmluZ19yZXN1bHRzLlJEUyIpCmBgYAoKIyMgUmVhZCBpbiBhbmQgZmlsdGVyIGRhdGEKCiMjIyBTYW1wbGUgbWV0YWRhdGEKCkxldCdzIHJlYWQgaW4gdGhlIHNhbXBsZSBtZXRhZGF0YSBhbmQgZ2V0IHRoZSBzYW1wbGUgaWRlbnRpZmllcnMgZm9yIG1lZHVsbG9ibGFzdG9tYSBzYW1wbGVzLgoKYGBge3IgcmVhZF9pbl9tZXRhZGF0YSwgbGl2ZSA9IFRSVUV9CiMgUmVhZCBpbiBtZXRhZGF0YSBUU1YKaGlzdG9sb2dpZXNfZGYgPC0gcmVhZF90c3YoaGlzdG9sb2dpZXNfZmlsZSkKCiMgT25seSBwdWxsIG91dCBzYW1wbGUgaWRlbnRpZmllcnMgKEtpZHNGaXJzdCBiaW9zcGVjaW1lbiBpZGVudGlmaWVycykgdGhhdAojIGNvcnJlc3BvbmQgdG8gbWVkdWxsb2JsYXN0b21hIHNhbXBsZXMKbWVkdWxsb2JsYXN0b21hX3NhbXBsZXMgPC0gaGlzdG9sb2dpZXNfZGYgJT4lCiAgZmlsdGVyKHNob3J0X2hpc3RvbG9neSA9PSAiTWVkdWxsb2JsYXN0b21hIikgJT4lCiAgcHVsbChLaWRzX0ZpcnN0X0Jpb3NwZWNpbWVuX0lEKQpgYGAKCiMjIyBSTkEtc2VxIGRhdGEKCmBgYHtyIHJlYWRfaW5fcm5hc2VxfQojIFJlYWQgaW4gdHJhbnNmb3JtZWQgUk5BLXNlcSBkYXRhCnJuYXNlcV9kZiA8LSByZWFkX3RzdihybmFzZXFfZmlsZSkKCiMgRm9yIG91ciBjbHVzdGVyaW5nIHZhbGlkYXRpb24gYW5hbHlzZXMsIHdlIHdhbnQgYSBtYXRyaXggb2Ygb25seSAKIyBtZWR1bGxvYmxhc3RvbWEgc2FtcGxlcyB3aGVyZSB0aGUgZ2VuZSBpZGVudGlmaWVycyBhcmUgcm93bmFtZXMgcmF0aGVyIHRoYW4KIyBpbiB0aGUgZmlyc3QgY29sdW1uCnJuYXNlcV9tYXQgPC0gcm5hc2VxX2RmICU+JQogICMgVGhpcyBtYWtlcyBzdXJlIHdlIHJldGFpbiBhbGwgb2YgdGhlIGNvbHVtbnMgd2l0aCBiaW9zcGVjaW1lbiBJRHMgdGhhdCAKICAjIGNvcnJlc3BvbmQgdG8gbWVkdWxsb2JsYXN0b21hIHNhbXBsZXMKICBzZWxlY3QoZ2VuZV9pZCwgYWxsX29mKG1lZHVsbG9ibGFzdG9tYV9zYW1wbGVzKSkgJT4lCiAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoImdlbmVfaWQiKSAlPiUKICBhcy5tYXRyaXgoKQpgYGAKCiMjIENsdXN0ZXIgdmFsaWRhdGlvbgoKIyMjIENvbnNlbnN1cyBjbHVzdGVyaW5nCgpUaGUgZmlyc3QgbWV0aG9kIHdlJ2xsIHVzZSBpcyBjYWxsZWQgY29uc2Vuc3VzIGNsdXN0ZXJpbmcuCkNvbnNlbnN1cyBjbHVzdGVyaW5nIHRvIGZpbmRzIHRoZSAiY29uc2Vuc3VzIiBhY3Jvc3MgbXVsdGlwbGUgcnVucyBvZiB0aGUgYWxnb3JpdGhtIHVzaW5nIGEgcmVzYW1wbGluZyBwcm9jZWR1cmUuCgpXZSdsbCB1c2UgdGhlIHBhY2thZ2UgW2BDb25zZW5zdXNDbHVzdGVyUGx1c2BdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvaHRtbC9Db25zZW5zdXNDbHVzdGVyUGx1cy5odG1sKSB0aGF0IHdlIGxvYWRlZCB1cCB0b3AuCgpUaGUgY29uc2Vuc3VzIGNsdXN0ZXJpbmcgbWV0aG9kb2xvZ3kgd2FzIGZpcnN0IGludHJvZHVjZWQgaW4gW01vbnRpIGV0IGFsLiBfTWFjaGluZSBMZWFybmluZ18uIDIwMDMuXShodHRwczovL2RvaS5vcmcvMTAuMTAyMy9BOjEwMjM5NDk1MDk0ODcpCgpDb25zZW5zdXMgY2x1c3RlcmluZyBpcyBvbmUgd2F5IHRvIGhlbHAgeW91IGRldGVybWluZSB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIGluIHlvdXIgZGF0YSwgYnV0IGl0IGlzIG5vdCB0aGUgb25seSBtZXRob2RvbG9neSBhdmFpbGFibGUuIApDaGVjayBvdXQgdGhlIFtEYXRhIE5vdmlhIGNvdXJzZSBfQ2x1c3RlciBWYWxpZGF0aW9uIEVzc2VudGlhbHNfIGJ5IEFsYm91a2FkZWwgS2Fzc2FtYmFyYV0oaHR0cHM6Ly93d3cuZGF0YW5vdmlhLmNvbS9lbi9jb3Vyc2VzL2NsdXN0ZXItdmFsaWRhdGlvbi1lc3NlbnRpYWxzLykgZm9yIGEgZGVlcGVyIGRpdmUuCgpgYGB7ciBjb25zZW5zdXNfY2x1c3RlcmluZ30KY2NfcmVzdWx0cyA8LSBDb25zZW5zdXNDbHVzdGVyUGx1cyhybmFzZXFfbWF0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heEsgPSAxNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNldHRpbmcgdGhpcyBzZWVkIGlzIG5lY2Vzc2FyeSBmb3IgdGhlIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgcmVzdWx0cyB0byBiZSByZXByb2R1Y2libGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMjAyMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbm5lckxpbmthZ2UgPSAiYXZlcmFnZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmluYWxMaW5rYWdlID0gImF2ZXJhZ2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3RhbmNlID0gInBlYXJzb24iKQpgYGAKCkl0IHNlZW1zIGxpa2UgdGhlcmUgYXJlIDMgbWFpbiBzdGFibGUgY2x1c3RlcnMgZnJvbSB0aGUgY29uc2Vuc3VzIGNsdXN0ZXJpbmcgcmVzdWx0cy4KQSBjbHVzdGVyIG9mIGEgZmV3IHNhbXBsZXMgbWF5IG5vdCBiZSB0aGF0IGhlbHBmdWwgaW4gcmVhY2hpbmcgb3VyIGFuYWx5c2lzIGdvYWxzLgoKTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGNsYXNzIGxhYmVscyBmb3IgX2tfID0gOS4KCmBgYHtyIGNjX2xhYmVscywgbGl2ZSA9IFRSVUV9CiMgdGFibGUoKSBjcmVhdGVzIGEgY29udGluZ2VuY3kgdGFibGUgb2YgY291bnRzCiMgCnRhYmxlKGNjX3Jlc3VsdHNbWzldXSRjb25zZW5zdXNDbGFzcykKYGBgCgooTm90ZTogdGhlIG51bWJlcmluZyBvZiB0aGUgY2x1c3RlcnMgaXMgYXJiaXRyYXJ5IGhlcmUuKQoKKipMZXQncyBjb25zaWRlciBjbHVzdGVycyAxLCAzLCBhbmQgNCBmb3IgZnVydGhlciBhbmFseXNpcy4qKgpCdXQgZmlyc3QsIHdlJ2xsIHNhdmUgdGhlIGVudGlyZXR5IG9mIHRoZSBjb25zZW5zdXMgY2x1c3RlcmluZyByZXN1bHRzIHRvIGZpbGUuCgpgYGB7ciBzYXZlX2NjX3Jlc3VsdHMsIGxpdmUgPSBUUlVFfQojIFdyaXRlIGNvbnNlbnN1cyBjbHVzdGVyaW5nIHJlc3VsdHMgdG8gZmlsZQp3cml0ZV9yZHMoY2NfcmVzdWx0cywgcGF0aCA9IGNjX3Jlc3VsdHNfZmlsZSkKYGBgCgpBbmQgbm93IHRvIGV4dHJhY3QgdGhlIHNhbXBsZXMgaW4gdGhlIGNsdXN0ZXJzIG9mIGludGVyZXN0LgpXaGF0IHdlIHVzZWQgYHRhYmxlKClgIG9uIGJlZm9yZSBpcyBhY3R1YWxseSBhIG5hbWVkIHZlY3Rvci4KCmBgYHtyIGV4dHJhY3RfY2x1c3Rlcl9sYWJlbHN9CmNjX2NsdXN0ZXJfbGFiZWxzIDwtIGNjX3Jlc3VsdHNbWzldXSRjb25zZW5zdXNDbGFzcwpoZWFkKGNjX2NsdXN0ZXJfbGFiZWxzKQpgYGAKCldlJ2xsIGV4dHJhY3QgdGhlIG5hbWVzIChiaW9zcGVjaW1lbiBJRHMpIGZvciBzYW1wbGVzIGluIGNsdXN0ZXJzIDEsIDMsIGFuZCA0LCB3aGljaCBjb250YWluIHRoZSBtYWpvcml0eSBvZiBzYW1wbGVzLgoKYGBge3Igc2FtcGxlX25hbWVzfQpzYW1wbGVfaW5kZXggPC0gd2hpY2goY2NfY2x1c3Rlcl9sYWJlbHMgJWluJSBjKDEsIDMsIDQpKQpzYW1wbGVzX2luX2NsdXN0ZXJzIDwtIG5hbWVzKGNjX2NsdXN0ZXJfbGFiZWxzKVtzYW1wbGVfaW5kZXhdCmBgYAoKQ2hlY2sgd2hhdCBwcm9wb3J0aW9uIG9mIHRvdGFsIHNhbXBsZXMgYXJlIGluIG9uZSBvZiB0aGVzZSB0aHJlZSBjbHVzdGVycy4KCmBgYHtyIHByb3BvcnRpb25faW5fY2x1c3RlcnN9CiMgbnVtYmVyIG9mIHNhbXBsZXMgdGhhdCBtZXQgb3VyIGxvZ2ljYWwgY3JpdGVyaW9uIGFib3ZlIGRpdmlkZWQgYnkgdG90YWwgCiMgbnVtYmVyIG9mIHNhbXBsZXMKbGVuZ3RoKHNhbXBsZV9pbmRleCkgLyBsZW5ndGgoY2NfY2x1c3Rlcl9sYWJlbHMpCmBgYAoKQW5kIG5vdyB3ZSdsbCBmaWx0ZXIgdGhlIFJOQS1zZXEgbWF0cml4LgoKYGBge3IgZmlsdGVyX3RvX2NsdXN0ZXJzLCBsaXZlID0gVFJVRX0KZmlsdGVyZWRfcm5hc2VxX21hdCA8LSBybmFzZXFfbWF0Wywgc2FtcGxlc19pbl9jbHVzdGVyc10KZGltKGZpbHRlcmVkX3JuYXNlcV9tYXQpCmBgYAoKIyMjIFNpbGhvdWV0dGUgY29lZmZpY2llbnQKClRoZSBzaWxob3VldHRlIGNvZWZmaWNpZW50IG9yIHZhbHVlIGlzIGEgbWVhc3VyZSBvZiBjbHVzdGVyIGNvbnNpc3RlbmN5IHRoYXQgcmFuZ2VzIGZyb20gLTEgdG8gMS4KSXQgaXMgY2FsY3VsYXRlZCBvbiBhIHBlci1zYW1wbGUgYmFzaXMgLS0gaXQgbWVhc3VyZXMgaG93IHNpbWlsYXIgYSBzYW1wbGUgaXMgdG8gdGhlIGNsdXN0ZXIgaXQncyBpbiBjb21wYXJlZCB0byBvdGhlciBjbHVzdGVycyAoW1dpa2lwZWRpYSBlbnRyeSBmb3Igc2lsaG91ZXR0ZV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvU2lsaG91ZXR0ZV8oY2x1c3RlcmluZykpKS4KCldlJ2xsIHVzZSB0aGUgW2BjbHVzdGVyYCBwYWNrYWdlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvY2x1c3Rlci9pbmRleC5odG1sKSB0byBjb21wdXRlIG91ciBzaWxob3VldHRlIHZhbHVlcyBmb3IgdGhlIGNsdXN0ZXJzIHdlIGlkZW50aWZpZWQgdXNpbmcgY29uc2Vuc3VzIGNsdXN0ZXJpbmcgYWJvdmUuCgpUaGUgYHNpbGhvdWV0dGUoKWAgZnVuY3Rpb24gdGFrZXMgdGhlIGNsYXNzIGxhYmVscyBhbmQgZGlzc2ltaWxhcml0aWVzLgpMZXQncyB1c2UgUGVhcnNvbiBjb3JyZWxhdGlvbiBhcyB3ZSBoYXZlIGJlZW4gdGhyb3VnaG91dC4KCmBgYHtyIHNhbWVfb3JkZXJ9CmFsbC5lcXVhbChuYW1lcyhjY19jbHVzdGVyX2xhYmVsc1tzYW1wbGVfaW5kZXhdKSwgCiAgICAgICAgICBjb2xuYW1lcyhmaWx0ZXJlZF9ybmFzZXFfbWF0KSkKYGBgCgpgYGB7ciBtYl9jb3JfYW5kX2Rpc3QsIGxpdmUgPSBUUlVFfQojIENhbGN1bGF0ZSB0aGUgUGVhcnNvbiBjb3JyZWxhdGlvbiBiZXR3ZWVuIHNhbXBsZXMgKGNvbHVtbnMgb2YgdGhlIG1hdHJpeCkKbWJfc2FtcGxlX2NvcnJlbGF0aW9uIDwtIGNvcihmaWx0ZXJlZF9ybmFzZXFfbWF0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJwZWFyc29uIikKIyBEaXNzaW1pbGFyaXR5IG1lYW5zIHdlIG5lZWQgdG8gc3VidHJhY3QgdGhlc2UgdmFsdWVzIGZyb20gMQptYl9zYW1wbGVfZGlzdCA8LSBhcy5kaXN0KDEgLSBtYl9zYW1wbGVfY29ycmVsYXRpb24pCmBgYAoKTGV0J3MgY2FsY3VsYXRlIHRoZSBzaWxob3VldHRlIHZhbHVlcyBhbmQgdGhlbiBwbG90IHRoZW0uCgpgYGB7ciBzaWxob3VldHRlfQpzaWxob3VldHRlX3Jlc3VsdHMgPC0gc2lsaG91ZXR0ZSh4ID0gY2NfY2x1c3Rlcl9sYWJlbHNbc2FtcGxlX2luZGV4XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzdCA9IG1iX3NhbXBsZV9kaXN0KQpwbG90KHNpbGhvdWV0dGVfcmVzdWx0cykKYGBgCgojIyMgQ29tcGFyZSBjbHVzdGVyaW5nIHJlc3VsdHMgdG8gZXh0ZXJuYWwgbWV0YWRhdGEgbGFiZWxzCgpUaGVyZSBhcmUgbXVsdGlwbGUgbWVkdWxsb2JsYXN0b21hIG1vbGVjdWxhciBzdWJ0eXBlcyBhbmQgdGhpcyBjbGFzc2lmaWNhdGlvbiBsYXJnZWx5IHJlbGllcyBvbiBnZW5lIGV4cHJlc3Npb24gZGF0YS4KQSBbbWVkdWxsb2JsYXN0b21hIHN1YnR5cGUgY2xhc3NpZmllcl0oaHR0cHM6Ly9naXRodWIuY29tL2QzYi1jZW50ZXIvbWVkdWxsby1jbGFzc2lmaWVyLXBhY2thZ2UpLCB3aGljaCBpcyBhbiBleGFtcGxlIG9mIF9zdXBlcnZpc2VkIG1hY2hpbmUgbGVhcm5pbmdfLCBoYXMgYmVlbiBhcHBsaWVkIHRvIHRoZSBtZWR1bGxvYmxhc3RvbWEgc2FtcGxlcyBpbmNsdWRlZCBpbiBPcGVuUEJUQS4KSG93IGRvIHRoZSBzdWJ0eXBlIGxhYmVscyBmcm9tIHRoaXMgY2xhc3NpZmllciAoaW4gdGhlIGBtb2xlY3VsYXJfc3VidHlwZWAgY29sdW1uIG9mIG91ciBzYW1wbGUgbWV0YWRhdGEpIHN0YWNrIHVwIHRvIHRoZSBjbHVzdGVycyB3ZSBpZGVudGlmaWVkIHdpdGggdW5zdXBlcnZpc2VkIG1ldGhvZHM/CgpMZXQncyBmaXJzdCBtYWtlIGEgZGF0YSBmcmFtZSB0aGF0IGhvbGRzIHRoZSBzdWJ0eXBlIGxhYmVscy4KV2UgY2FuIHVzZSB0aGlzIGJvdGggdG8gY29tcGFyZSBvdXIgdW5zdXBlcnZpc2VkIGNsdXN0ZXJpbmcgcmVzdWx0cyB0byB0aGUgc3VidHlwZSBsYWJlbHMgYW5kIGZvciBzb21lIHBsb3R0aW5nIGRvd25zdHJlYW0uCgpgYGB7ciBzdWJ0eXBlX2RmfQptYl9tb2xlY3VsYXJfc3VidHlwZV9kZiA8LSBoaXN0b2xvZ2llc19kZiAlPiUKICBmaWx0ZXIoc2hvcnRfaGlzdG9sb2d5ID09ICJNZWR1bGxvYmxhc3RvbWEiKSAlPiUKICBzZWxlY3QoS2lkc19GaXJzdF9CaW9zcGVjaW1lbl9JRCwgbW9sZWN1bGFyX3N1YnR5cGUpCmBgYAoKQWRkIHRoZSBjb25zZW5zdXMgY2x1c3RlcmluZyBsYWJlbHMuCgpgYGB7ciBzdWJ0eXBlX2NjLCBsaXZlID0gVFJVRX0KIyBDcmVhdGUgYSBkYXRhIGZyYW1lIHRoYXQgY29udGFpbnMgdGhlIGNvbnNlbnN1cyBjbHVzdGVyIHJlc3VsdHMgYW5kIGpvaW4KIyBpdCB3aXRoIHRoZSBkYXRhIGZyYW1lIG9mIG1vbGVjdWxhciBzdWJ0eXBlIGxhYmVscwpjY19kZiA8LSBkYXRhLmZyYW1lKGNjX2NsdXN0ZXJfbGFiZWxzKSAlPiUKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiS2lkc19GaXJzdF9CaW9zcGVjaW1lbl9JRCIpICU+JQogIGlubmVyX2pvaW4obWJfbW9sZWN1bGFyX3N1YnR5cGVfZGYpCmBgYAoKRG8gdGhlIGNvbnNlbnN1cyBjbHVzdGVyaW5nIHJlc3VsdHMgYWdyZWUgd2l0aCB0aGUgbW9sZWN1bGFyIHN1YnR5cGUgbGFiZWxzPwoKYGBge3IgY2Nfc3VidHlwZV90YWJsZX0KdGFibGUoY2NfZGYkY2NfY2x1c3Rlcl9sYWJlbHMsIGNjX2RmJG1vbGVjdWxhcl9zdWJ0eXBlKQpgYGAKCkhtLi4uIHRoZXJlJ3Mgc29tZSBhZ3JlZW1lbnQgd2l0aCB0aGUgc3VidHlwZSBsYWJlbHMgYnV0IGl0J3Mgbm90IHBlcmZlY3QuIApXaHkgbWlnaHQgdGhhdCBiZT8KTGV0J3Mgc3RhcnQgYnkgbG9va2luZyBhdCB0aGUgb3ZlcnZpZXcgZmlndXJlIGZvciB0aGUgbWVkdWxsb2JsYXN0b21hIGNsYXNzaWZpZXIuCgohW10oaHR0cHM6Ly9naXRodWIuY29tL2QzYi1jZW50ZXIvbWVkdWxsby1jbGFzc2lmaWVyLXBhY2thZ2UvcmF3L2FiYTI1YjZhY2MwZjhkYWIxMWY0NmRiNjk1NWEzOGZmYWMzZDAxNDAvaW1hZ2VzL3dvcmtmbG93LnBuZykKCldlIHVzZWQgYSBkaWZmZXJlbnQgbWVhc3VyZSAoVlNUIHZhbHVlcyB2cy4gRlBLTSkgYW5kIHVzZWQgYWxsIGZlYXR1cmVzLiAKCiMjIExvdy1kaW1lbnNpb25hbCByZXByZXNlbnRhdGlvbgoKTGV0J3Mgc2VlIGlmIGxvdy1kaW1lbnNpb25hbCByZXByZXNlbnRhdGlvbnMgYWdyZWUgd2l0aCBvdXIgb3RoZXIgdW5zdXBlcnZpc2VkIHJlc3VsdHMuCkZpcnN0LCBsZXQncyBmaW5kIHRoZSBoaWdoIHZhcmlhbmNlIGdlbmVzOyB3ZSdsbCBwZXJmb3JtIGRpbWVuc2lvbiByZWR1Y3Rpb24gb24gdGhvc2UgZ2VuZXMgb25seS4KCmBgYHtyIGdlbmVfdmFyaWFuY2V9CiMgQ2FsY3VsYXRlIHZhcmlhbmNlCmdlbmVfdmFyaWFuY2UgPC0gbWF0cml4U3RhdHM6OnJvd1ZhcnMocm5hc2VxX21hdCkKIyBGaW5kIHRoZSB2YWx1ZSB0aGF0IHdlJ2xsIHVzZSBhcyBhIHRocmVzaG9sZCB0byBmaWx0ZXIgdGhlIHRvcCA1JQp2YXJpYW5jZV90aHJlc2hvbGQgPC0gcXVhbnRpbGUoZ2VuZV92YXJpYW5jZSwgMC45NSkKIyBSb3cgaW5kaWNlcyBvZiBoaWdoIHZhcmlhbmNlIGdlbmVzCmhpZ2hfdmFyaWFuY2VfaW5kZXggPC0gd2hpY2goZ2VuZV92YXJpYW5jZSA+IHZhcmlhbmNlX3RocmVzaG9sZCkKYGBgCgojIyMgVU1BUAoKRmlyc3QsIGxldCdzIHVzZSBVTUFQLgoKYGBge3IgcnVuX3VtYXB9CiMgU2V0IHNlZWQgZm9yIHJlcHJvZHVjaWJsZSBVTUFQIHJlc3VsdHMKc2V0LnNlZWQoMjAyMCkKIyB1bWFwKCkgZXhwZWN0cyBmZWF0dXJlcyAoZ2VuZXMpIHRvIGJlIGNvbHVtbnMsIHNvIHdlIGhhdmUgdG8gdXNlIHQoKQp1bWFwX3Jlc3VsdHMgPC0gdW1hcDo6dW1hcCh0KHJuYXNlcV9tYXRbaGlnaF92YXJpYW5jZV9pbmRleCwgXSkpCmBgYAoKVGhlIFVNQVAgY29vcmRpbmF0ZXMgYXJlIGluIHRoZSBgbGF5b3V0YCBlbGVtZW50IG9mIHRoZSBsaXN0IHJldHVybmVkIGJ5IGB1bWFwOjp1bWFwKClgLgoKYGBge3IgbWFrZV91bWFwX2RmLCBsaXZlID0gVFJVRX0KIyBNYWtlIGEgZGF0YSBmcmFtZSBvZiB0aGUgbGF5b3V0IHJlc3VsdHMgYW5kIGpvaW4gd2l0aCBtb2xlY3VsYXIgc3VidHlwZSAKIyBkYXRhIGZyYW1lCnVtYXBfcGxvdF9kZiA8LSBkYXRhLmZyYW1lKHVtYXBfcmVzdWx0cyRsYXlvdXQpICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJLaWRzX0ZpcnN0X0Jpb3NwZWNpbWVuX0lEIikgJT4lCiAgaW5uZXJfam9pbihtYl9tb2xlY3VsYXJfc3VidHlwZV9kZikKYGBgCgpMZXQncyBtYWtlIGEgc2NhdHRlciBwbG90IGFuZCBjb2xvciBvdXIgc2FtcGxlcyBieSB0aGUgc3VidHlwZSBsYWJlbHMuCgpgYGB7ciB1bWFwX3NjYXR0ZXJ9CnVtYXBfcGxvdF9kZiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBYMSwgCiAgICAgICAgICAgICB5ID0gWDIsCiAgICAgICAgICAgICBjb2xvciA9IG1vbGVjdWxhcl9zdWJ0eXBlKSkgKwogIGdlb21fcG9pbnQoKSArCiAgY29sb3JibGluZHI6OnNjYWxlX2NvbG9yX09rYWJlSXRvKCkgKwogIHRoZW1lX2J3KCkgKwogIHhsYWIoIlVNQVAxIikgKwogIHlsYWIoIlVNQVAyIikKYGBgCgojIyMgUENBIAoKTm93LCB3ZSdsbCBicmllZmx5IHNob3cgeW91IGhvdyB0byB1c2UgYnVpbHQtaW4gZnVuY3Rpb25zIGZvciBQQ0Egb24gYW55IG1hdHJpeCB0aGF0J3Mgbm90IGluIGEgc3BlY2lhbGl6ZWQgb2JqZWN0IGZvciBzb21lIGtpbmQgb2YgZ2Vub21pYyBkYXRhLgoKYGBge3IgcnVuX3BjYX0KIyBMaWtlIHVtYXAoKSwgcHJjb21wKCkgZXhwZWN0cyB0aGUgZmVhdHVyZXMgdG8gYmUgY29sdW1ucwpwY2FfcmVzdWx0cyA8LSBwcmNvbXAodChybmFzZXFfbWF0W2hpZ2hfdmFyaWFuY2VfaW5kZXgsIF0pLCAKICAgICAgICAgICAgICAgICAgICAgIHNjYWxlID0gVFJVRSkKIyBUaGUgbG93LWRpbWVuc2lvbmFsIHJlcHJlc2VudGF0aW9uIGlzIHJldHVybmVkIGluIHgKcGNhX3Jlc3VsdHMkeFsxOjYsIDE6Nl0KYGBgCgpMZXQncyBnZXQgdGhlIGZpcnN0IDEwIFBDcyByZWFkeSBmb3IgcGxvdHRpbmcuCgpgYGB7ciBwY2FfZGYsIGxpdmUgPSBUUlVFfQpwY2FfcGxvdF9kZiA8LSBkYXRhLmZyYW1lKHBjYV9yZXN1bHRzJHhbLCAxOjEwXSkgJT4lCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oIktpZHNfRmlyc3RfQmlvc3BlY2ltZW5fSUQiKSAlPiUKICBpbm5lcl9qb2luKG1iX21vbGVjdWxhcl9zdWJ0eXBlX2RmKQpgYGAKCkFuZCBub3cgbWFrZSBhIHNjYXR0ZXJwbG90IG9mIFBDMSBhbmQgUEMyLgoKYGBge3IgcGNhX3NjYXR0ZXJfMSwgbGl2ZSA9IFRSVUV9CnBjYV9wbG90X2RmICU+JQogIGdncGxvdChhZXMoeCA9IFBDMSwgCiAgICAgICAgICAgICB5ID0gUEMyLAogICAgICAgICAgICAgY29sb3IgPSBtb2xlY3VsYXJfc3VidHlwZSkpICsKICBnZW9tX3BvaW50KCkgKwogIGNvbG9yYmxpbmRyOjpzY2FsZV9jb2xvcl9Pa2FiZUl0bygpICsKICB0aGVtZV9idygpICsKICB4bGFiKCJQQzEiKSArCiAgeWxhYigiUEMyIikKYGBgCgpXaGF0IGFib3V0IFBDMiBhbmQgUEMzPwoKYGBge3IgcGNhX3NjYXR0ZXJfMiwgbGl2ZSA9IFRSVUV9CnBjYV9wbG90X2RmICU+JQogIGdncGxvdChhZXMoeCA9IFBDMiwgCiAgICAgICAgICAgICB5ID0gUEMzLAogICAgICAgICAgICAgY29sb3IgPSBtb2xlY3VsYXJfc3VidHlwZSkpICsKICBnZW9tX3BvaW50KCkgKwogIGNvbG9yYmxpbmRyOjpzY2FsZV9jb2xvcl9Pa2FiZUl0bygpICsKICB0aGVtZV9idygpICsKICB4bGFiKCJQQzIiKSArCiAgeWxhYigiUEMzIikKYGBgCgpgc3VtbWFyeSgpYCB3aWxsIHJlcG9ydCB0aGUgcHJvcG9ydGlvbiBvZiB2YXJpYW5jZSBleHBsYWluZWQgYnkgZWFjaCBwcmluY2lwYWwgY29tcG9uZW50LgpCeSBhY2Nlc3NpbmcgdGhlIGBpbXBvcnRhbmNlYCBlbGVtZW50IHdpdGggYDxzdW1tYXJ5IHJlc3VsdHM+JGltcG9ydGFuY2VgLCB3ZSBjYW4gdXNlIGluZGV4aW5nIHRvIG9ubHkgbG9vayBhdCB0aGUgZmlyc3QgMTAgUENzLgoKYGBge3IgcGNhX3N1bW1hcnl9CiMgU2F2ZSBzdW1tYXJ5IG9mIHRoZSBQQ0EgcmVzdWx0cwpwY2Ffc3VtbWFyeSA8LSBzdW1tYXJ5KHBjYV9yZXN1bHRzKQojIEltcG9ydGFuY2UgaW5mb3JtYXRpb24gZm9yIHRoZSBmaXJzdCAxMCBQQ3MKcGNhX2ltcG9ydGFuY2UgPC0gcGNhX3N1bW1hcnkkaW1wb3J0YW5jZVssIDE6MTBdCnBjYV9pbXBvcnRhbmNlCmBgYAoKW2BwbG90bHlgXShodHRwczovL3Bsb3RseS5jb20vci8pIGlzIGEgcGFja2FnZSB0aGF0IGFsbG93cyB1cyB0byBwbG90IGludGVyYWN0aXZlIDNEIHNjYXR0ZXJwbG90cy4KCmBgYHtyIDNkX3NjYXR0ZXJ9CiMgcGxvdF9seSBpcyBraW5kIG9mIGxpa2UgZ2dwbG90KCksIGJ1dCBpdCBkb2Vzbid0IHJlcXVpcmUgdXMgdG8gc3BlY2lmeSB0aGUgCiMgYWVzdGhldGljcyBvciB0eXBlcyAoZS5nLiwgZ2VvbXMpIHNlcGFyYXRlbHkgaW4gdGhlIHNhbWUgd2F5CnBjYV8zZF9wbG90IDwtIHBsb3RfbHkoZGF0YSA9IHBjYV9wbG90X2RmLAogICAgICAgICAgICAgICAgICAgICAgIHggPSB+IFBDMSwKICAgICAgICAgICAgICAgICAgICAgICB5ID0gfiBQQzIsCiAgICAgICAgICAgICAgICAgICAgICAgeiA9IH4gUEMzLAogICAgICAgICAgICAgICAgICAgICAgIG1vZGUgPSAibWFya2VycyIsCiAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLAogICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gfiBtb2xlY3VsYXJfc3VidHlwZSwKICAgICAgICAgICAgICAgICAgICAgICBjb2xvcnMgPSBjb2xvcmJsaW5kcjo6cGFsZXR0ZV9Pa2FiZUl0b1sxOjRdKQoKIyBBZGQgYXhpcyBsYWJlbHMKcGNhXzNkX3Bsb3QgPC0gcGNhXzNkX3Bsb3QgJT4lIAogIGxheW91dChzY2VuZSA9IGxpc3QoCiAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiUEMxIiksCiAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiUEMyIiksCiAgICB6YXhpcyA9IGxpc3QodGl0bGUgPSAiUEMzIikKICApKQoKcGNhXzNkX3Bsb3QKYGBgCgojIyBTZXNzaW9uIEluZm8KCmBgYHtyIHNlc3Npb25faW5mb30Kc2Vzc2lvbkluZm8oKQpgYGAK